use std::fs;
use std::path::Path;

use crate::api::oss;
use crate::config::CONTEXT;
use crate::global::{DpmCell, DpmError, RespVO};
use crate::subcmd::basecmd::BaseCmd;
use crate::subcmd::{get_exec_path, is_ymlconf, ymlconf_to_model};
use async_trait::async_trait;
use rcmd_core::clap::ArgMatches;
use rcmd_core::ignore::WalkBuilder;
use rcmd_core::log::{debug, error, info};
use rcmd_core::util::filesys::{file_md5, zip_dir};
use rcmd_core::zip;

const INNNER_IGNORE: [&'static str; 1] = ["LIST HERE"];

struct Publish<'a> {
    //要发布的路径
    path: &'a str,
}

impl<'a> Publish<'a> {
    fn new(path: &'a str) -> Self {
        Publish { path }
    }

    pub async fn api_remote_add(dpm: &DpmCell) -> Result<RespVO<()>, surf::Error> {
        let uri = format!("{uri_base}/api/dpm/add", uri_base = CONTEXT.uri_base);
        let ret = surf::put(&uri).body_json(dpm);
        debug!("api req the uri...\n ->{}", uri.clone().as_str());
        let o = ret.unwrap().recv_json::<RespVO<()>>().await;
        return o;
    }

    pub async fn api_remote_info(dpm: &DpmCell) -> Result<RespVO<DpmCell>, surf::Error> {
        let _n = dpm.name.to_owned();
        let uri = format!(
            "{}/api/dpm/info?name={}&ver={}",
            CONTEXT.uri_base,
            &dpm.name.to_owned(),
            &dpm.ver.to_owned()
        );
        let ret: Result<RespVO<DpmCell>, surf::Error> =
            surf::get(&uri).recv_json::<RespVO<DpmCell>>().await;
        ret
    }

    pub fn get_oss_download_url(info: &DpmCell) -> String {
        //todo 区分是否是公共插件，还是私有插件
        let remote_path = format!(
            "https://{bucket}.{endpoint}/{relpath}",
            bucket = &CONTEXT.oss_config.bucket,
            endpoint = &CONTEXT.oss_config.end_point,
            relpath = crate::subcmd::oss_relpath(info)
        );
        remote_path
    }
}

#[async_trait]
impl BaseCmd for Publish<'_> {
    async fn run(&self) -> Result<(), DpmError> {
        Publish::load_setting();
        //env
        let o = is_ymlconf(self.path);
        if !o {
            return Err(DpmError::NotInitYet);
        }

        //chk user
        //todo 暂时不做用户鉴权 等ding auth
        //chk remote
        let mut r = ymlconf_to_model(self.path)?;
        let r_n = Publish::api_remote_info(&r.base).await;
        if r_n.is_err() {
            return Err(DpmError::ApiFailed(r_n.err().unwrap().to_string()));
        }
        let ret = r_n.unwrap();
        println!("{:#?}", &ret);
        if ret.code != 1004 && ret.code != 0 {
            return Err(DpmError::ApiFailed(format!(
                "code={}, msg={}",
                &ret.code, &ret.msg
            )));
        };
        if ret.data.is_some() {
            return Err(DpmError::ApiFailed("already exist!".to_string()));
        }

        /*
        zip+upload
        */
        info!("zip the package...");
        let pf = Path::new(self.path);
        let fname = format!("{}@{}.zip", &r.base.name.as_str(), &r.base.ver.as_str());

        let exe_dir = std::env::current_exe()
            .unwrap()
            .parent()
            .unwrap()
            .join(".dpm_download");
        let exe_path = exe_dir.as_path();
        let is_d = Path::is_dir(&exe_path);
        if !is_d {
            let _ = fs::create_dir(&exe_path).unwrap();
        }
        let zipfile_pb = &exe_path.join(&fname);
        let zipfile_path = zipfile_pb.as_path();
        debug!("zip package to dir {}", zipfile_path.display());
        let zipfile = std::fs::File::create(zipfile_path).unwrap();
        let ws2 = WalkBuilder::new(pf);
        let ret = &mut ws2.build().into_iter().filter_map(
            |v: Result<rcmd_core::ignore::DirEntry, rcmd_core::ignore::Error>| {
                let c: rcmd_core::ignore::DirEntry = v
                    .to_owned()
                    .expect("[cmd-publish]walk dir to check ignore-files failed!");
                if INNNER_IGNORE.contains(
                    &c.file_name()
                        .to_str()
                        .expect("[cmd-publish]walk dir get file name failed!"),
                ) {
                    info!("ignoreing file ...<{:?}>", &c.file_name().to_str());
                    return None;
                }
                v.ok()
            },
        );
        let zr = zip_dir(
            ret,
            pf.to_str().unwrap(),
            zipfile,
            zip::CompressionMethod::BZIP2,
        );
        if zr.is_err() {
            return Err(DpmError::ApiFailed("disable zip file ".to_string()));
        }
        //md5
        let f_str = zipfile_path.as_os_str().to_str().unwrap();
        let md5 = file_md5(f_str);
        debug!("file md5 is {}", md5);
        let _ = &r.set_md5(Some(md5));
        //url
        let remote_url = Self::get_oss_download_url(&r.base);
        r.base.url = Some(remote_url);

        //upload to oss->
        let oss_path = crate::subcmd::oss_relpath(&r.base);
        info!("oss uploading to remote dir ->{}...", oss_path);
        let ret = oss::upload(
            &CONTEXT.oss_config,
            zipfile_path.display().to_string().as_str(),
            oss_path.as_str(),
        )
        .await;

        if ret.is_some() {
            info!("done!");
        }
        /*
        remote api
        */
        info!("api to add the package...");
        let r = Publish::api_remote_add(&r.base).await;
        if let Err(err) = r {
            return Err(DpmError::ApiFailed(format!(
                "api to insert record into db failed!!\n{}",
                err.to_string()
            )));
        } else {
            let resp = r.unwrap();
            if resp.code != 0 {
                return Err(DpmError::ApiFailed(resp.msg));
            }
        }
        info!("suc！");
        Ok(())
    }
}

pub async fn handle(_: &ArgMatches) {
    let p = get_exec_path();
    let cmd = &Publish::new(p.as_str());
    cmd.run().await.unwrap_or_else(|e| {
        error!("{}", e.to_string().as_str());
    });
}

#[tokio::test]
async fn test_publish() {
    let cur_dir =
        std::path::Path::new("C:\\Users\\Administrator\\Desktop\\dpm\\tracer-inspector\\win");
    println!("cur dir is {}", cur_dir.display());

    let pub_dir = cur_dir.to_str().unwrap();
    let cmd = Publish::new(pub_dir);
    let ret = cmd.run().await;
    match ret {
        Ok(_) => {
            println!("kokokokokoko")
        }
        Err(v) => {
            println!("{}", v.to_string())
        }
    };
}
